View Javadoc

1   /*
2    * Created on 22-Oct-2004
3    */
4   package uk.ac.roe.antigen.builder;
5   
6   import java.io.BufferedInputStream;
7   import java.io.File;
8   import java.io.FileInputStream;
9   import java.io.FileNotFoundException;
10  import java.io.IOException;
11  import java.io.InputStream;
12  import java.net.MalformedURLException;
13  import java.net.URL;
14  import java.net.URLDecoder;
15  import java.util.Enumeration;
16  import java.util.Hashtable;
17  import java.util.Iterator;
18  import java.util.List;
19  import java.util.Properties;
20  import java.util.logging.Handler;
21  import java.util.logging.Level;
22  import java.util.logging.Logger;
23  
24  import org.apache.commons.cli.CommandLine;
25  import org.apache.commons.cli.HelpFormatter;
26  import org.apache.commons.cli.Option;
27  import org.apache.commons.cli.OptionBuilder;
28  import org.apache.commons.cli.Options;
29  import org.apache.commons.cli.ParseException;
30  import org.apache.commons.cli.Parser;
31  import org.apache.commons.cli.PosixParser;
32  import org.apache.tools.ant.BuildException;
33  import org.apache.tools.ant.BuildLogger;
34  import org.apache.tools.ant.DefaultLogger;
35  import org.apache.tools.ant.Project;
36  import org.apache.tools.ant.ProjectHelper;
37  import org.apache.tools.ant.Target;
38  import org.apache.tools.ant.input.InputHandler;
39  import org.apache.velocity.exception.MethodInvocationException;
40  import org.apache.velocity.exception.ParseErrorException;
41  import org.apache.velocity.exception.ResourceNotFoundException;
42  
43  import uk.ac.roe.antigen.ant.BuildGrabber;
44  import uk.ac.roe.antigen.ant.GeneralInputHandler;
45  import uk.ac.roe.antigen.ant.TaskTreeCalcuator;
46  import uk.ac.roe.antigen.dialogs.AbortDialog;
47  import uk.ac.roe.antigen.dialogs.BuildLoggerFrame;
48  import uk.ac.roe.antigen.dialogs.InfoFrame;
49  import uk.ac.roe.antigen.dialogs.SplashScreen;
50  import uk.ac.roe.antigen.dialogs.TargetChoiceFrame;
51  import uk.ac.roe.antigen.textcomponents.DefaultPromptingInputHandler;
52  import uk.ac.roe.antigen.textcomponents.TextInfoDisplay;
53  import uk.ac.roe.antigen.textcomponents.TextTargetChooser;
54  import uk.ac.roe.antigen.utils.Config;
55  import uk.ac.roe.antigen.utils.CopyableFile;
56  
57  /***
58   * Main controller, loads up the build file, and fires it at Ant, setting up our
59   * Custom loggers and inputhandlers.
60   * 
61   * @todo refactor and make it less embarrassing
62   * 
63   * @author jdt
64   */
65  public class Installer {
66  
67      private static final String ANTIGEN_HOME = "antigen.home";
68  
69      private static final String ANTIGEN_JARFILE = "antigen.jarfile";
70  
71      /***
72       * Logger for this class
73       */
74      private static final Logger logger = Logger.getLogger(Installer.class.getName());
75  
76      private static final String PROPERTY_OPTION = "D";
77      
78      private static final String PROXY_OPTION="p";
79      
80      private static final String PROPERTYFILE_OPTION = "f";
81  
82      private static final String CMDLINE_OPTION = "c";
83  
84      private static final String HELP_OPTION = "h";
85  
86      private static final String DEBUG_OPTION = "d";
87  
88      private static final String PROXYUSER_OPTION = "u";
89  
90      private static final String PROPERTY_ANTIGEN_OPTION = "A";
91  
92      public static void main(String[] args) throws Throwable {
93  
94          
95          CommandLine line = parseCommandLine(args);
96          
97          Config.load("/config.properties");
98  
99          //Override Antigen properties if requested
100         if (line.hasOption(PROPERTY_ANTIGEN_OPTION)) {
101             String[] keyValuePairs=line.getOptionValues(PROPERTY_ANTIGEN_OPTION);
102             logger.fine("Found "+keyValuePairs.length/2+" antigen properties");
103             for (int i=0;i<keyValuePairs.length/2;++i) {
104                     String key=keyValuePairs[2*i];
105                     String value=keyValuePairs[2*i+1];
106                     logger.fine("Overriding property "+key+"="+value);
107                     Config.setProperty(key,value);
108             }
109         }
110         
111         String levelString = Config.getProperty("antigen.loglevel","SEVERE");        
112         boolean debug = line.hasOption(DEBUG_OPTION);
113         if (debug) {
114             levelString = line.getOptionValue(DEBUG_OPTION);
115         }
116         Level logLevel = Level.parse(levelString);
117 
118         // Crank up the logging level, and that of the (assumed only)
119         // handler
120         Logger rootLogger = Logger.getLogger("");
121         rootLogger.setLevel(logLevel);
122         Handler[] handlers = rootLogger.getHandlers();
123         assert handlers.length != 0;
124         Handler handler = handlers[0];
125         handler.setLevel(logLevel);
126         logger.info("Debug mode: "+levelString);
127         
128         boolean textOnly = line.hasOption(CMDLINE_OPTION);
129         logger.info("Text only mode: " + textOnly);
130 
131         Properties sysprops = System.getProperties();       
132         if (line.hasOption(PROXY_OPTION)) {
133             logger.fine("Setting proxy");
134             String hostAndPort = line.getOptionValue(PROXY_OPTION);
135             String[] hostAndPortSplit = hostAndPort.split(":");
136             String host = hostAndPortSplit[0];
137             String port = hostAndPortSplit.length>1 ? hostAndPortSplit[1] : "80";
138             logger.fine("Proxying through "+host+" on port "+port);
139 
140            // sysprops.put("proxySet", "true");
141             sysprops.put("http.proxyHost", host);
142             sysprops.put("http.proxyPort", port);  
143             if (line.hasOption(PROXYUSER_OPTION)) {
144                 String userAndPass = line.getOptionValue(PROXYUSER_OPTION);
145                 String[] userAndPassSplit = userAndPass.split(":");
146                 String proxyUser = userAndPassSplit[0];
147                 String proxyPassword = userAndPassSplit.length>1 ? userAndPassSplit[1] : "";
148                 sysprops.put("http.proxyUser", proxyUser);
149                 sysprops.put("http.proxyPassword", proxyPassword);
150             } 
151         }
152 
153         File thisJar;
154         logger.fine("Getting this jar");
155         try {
156             thisJar = new Installer().getEnclosingJar(); //yuck
157         } catch (FileNotFoundException fe) {
158             logger.warning(fe.getMessage());
159             logger.warning("Unable to set property "+ANTIGEN_JARFILE);
160             thisJar = new File("Unknown");
161         }
162         
163         //Show a dialog, if necessary
164         if ((Config.getProperty("antigen.splash.logo")!=null || Config.getProperty("antigen.splash.text")!=null) && !textOnly) {
165             new SplashScreen().show();
166         }
167         
168         
169         
170         
171 
172 
173         //      Grab all the build files, and place them in a temporary directory
174         String buildSrcType = Config.getProperty("build.src.type");
175         String buildSrcLoc = Config.getProperty("build.src.location");
176         assert buildSrcLoc != null;
177         CopyableFile tmpDir = null;
178         
179         String buildDir = Config.getProperty("antigen.tmp.dir");
180         try {
181             BuildGrabber buildGrabber;
182             if (buildDir!=null) {
183                 buildGrabber= new BuildGrabber(new File(buildDir));
184             } else {
185                 buildGrabber= new BuildGrabber();
186             }
187             if ("dir".equals(buildSrcType)) {
188                 //useful for testing
189                 logger.fine("Grabbing from directory " + buildSrcLoc);
190                 tmpDir = buildGrabber.grab(new File(buildSrcLoc));
191             } else if ("classpath".equals(buildSrcType)) {
192                 //grab a jar file off the classpath - useful if everything's
193                 // bundled in one big jar
194                 logger.fine("Grabbing from the classpath off " + buildSrcLoc);
195                 tmpDir = buildGrabber.grab(buildSrcLoc);
196             } else if ("self".equals(buildSrcType)) {
197                 logger.fine("Grabbing from self");
198                 tmpDir = buildGrabber.grab(thisJar.toURL());
199             } else {
200                 //default to URL
201                 tmpDir = buildGrabber.grab(new URL(buildSrcLoc));
202             }
203         } catch (MalformedURLException e1) {
204             logger.severe("Unable to load build script from " + buildSrcType + " " + buildSrcLoc);
205             logger.severe("Sorry, but I can't continue");
206             logger.fine(e1.getMessage());
207             throw e1;
208         } catch (IOException e1) {
209             logger.severe("Unable to load build script from " + buildSrcType + " " + buildSrcLoc);
210             logger.severe("Sorry, but I can't continue");
211             logger.fine(e1.getMessage());
212             throw e1;
213         }
214 
215         BuildLoggerFrame builddialog = null;
216         if (!textOnly) {
217             builddialog = new BuildLoggerFrame();
218         }
219         showInfoDialog(builddialog, sysprops, "antigen.intro.text", "antigen.intro.template", textOnly);
220 
221         File buildFile = new File(tmpDir, "build.xml");
222 
223         Project project = new Project();
224         project.init();
225         ProjectHelper.configureProject(project, buildFile);
226 
227         //Add in some properties that might be useful to a script
228         
229         project.setProperty(ANTIGEN_HOME, System.getProperty("user.dir")); //where we're running
230         project.setProperty(ANTIGEN_JARFILE, thisJar.getAbsolutePath());
231 
232         //Add in some properties if requested
233         //Single properties
234         if (line.hasOption(PROPERTY_OPTION)) {
235             String[] keyValuePairs=line.getOptionValues(PROPERTY_OPTION);
236             logger.fine("Found "+keyValuePairs.length/2+" command line properties");
237             for (int i=0;i<keyValuePairs.length/2;++i) {
238                     String key=keyValuePairs[2*i];
239                     String value=keyValuePairs[2*i+1];
240                     logger.fine("Setting property "+key+"="+value);
241                     project.setProperty(key, value);
242             }
243         }
244         
245         //property files
246         if (line.hasOption(PROPERTYFILE_OPTION)) {
247             logger.fine("Attempting to load properties from a file");
248             File inputFile = new File(line.getOptionValue(PROPERTYFILE_OPTION));
249             //Really ought to be a utility method somewhere to do this kind of
250             // shit
251             Properties props = new Properties();
252             InputStream is = null;
253             try {
254                 is = new BufferedInputStream(new FileInputStream(inputFile));
255                 props.load(is);
256             } catch (IOException ie) {
257                 logger.warning("Was unable to load properties file " + ie.getMessage() + "...continuing");
258             } finally {
259                 if (is != null)
260                     try {
261                         is.close();
262                     } catch (IOException swallow) {
263                         // Well, I tried
264                     }
265             }
266             Enumeration enumer = props.keys();
267             while (enumer.hasMoreElements()) {
268                 String key = (String) enumer.nextElement();
269                 logger.fine("Putting property " + key + "=" + props.getProperty(key));
270                 project.setProperty(key, props.getProperty(key));
271             }
272         }
273 
274         // Select which targets to run
275         String targetName = project.getDefaultTarget();
276 
277         String anyChosenTargets = Config.getProperty(TargetChoiceFrame.ANTIGEN_TARGETS);
278         if (anyChosenTargets != null) {
279             if (anyChosenTargets.split(",").length == 1) {
280                 //only one choice
281                 targetName = anyChosenTargets; //@TODO need to check this
282                                                // exists
283             } else {
284                 TargetChooser targetChoiceFrame;
285                 if (textOnly) {
286                     targetChoiceFrame = new TextTargetChooser(project);
287                 } else {
288                     targetChoiceFrame = new TargetChoiceFrame(project);
289                 }
290                 List targetNames = targetChoiceFrame.showAndWaitForResponse();
291                 Target master = new Target();
292 
293                 //master.
294                 Iterator it = targetNames.iterator();
295                 while (it.hasNext()) {
296                     String next = (String) it.next();
297                     logger.fine("Adding target " + next);
298                     master.addDependency(next);
299                 }
300                 final String masterTargetName = "master";
301                 master.setName(masterTargetName);
302                 master.setDescription("One project to rule them all");
303                 //     master.setProject(project);
304                 project.addTarget(masterTargetName, master);
305                 targetName = masterTargetName;
306                 project.init();
307             }
308         }
309 
310         //Get total number of tasks to be run
311 
312         int numberOfTasks = new TaskTreeCalcuator(project).getNumberOfTasksToRun(targetName);
313         logger.fine("main(String[])" + numberOfTasks);
314 
315         InputHandler inputHandler;
316         BuildLogger buildLogger;
317         if (!textOnly) {
318             builddialog.setVisible(true);
319             builddialog.setNumberOfTasks(numberOfTasks); //used to callibrate
320                                                          // progress bar
321             BuildLogger dialogBuildLogger = builddialog.getBuildLogger();
322 
323             inputHandler = new GeneralInputHandler(builddialog);
324             buildLogger = dialogBuildLogger;
325 
326         } else {
327             inputHandler = new DefaultPromptingInputHandler();
328             DefaultLogger defaultListener = new DefaultLogger();
329             buildLogger = defaultListener;
330             buildLogger.setOutputPrintStream(System.out);
331             buildLogger.setErrorPrintStream(System.err);
332         }
333         
334         int antOutputLevel = Integer.parseInt(Config.getProperty("antigen.antoutput","2"));
335         buildLogger.setMessageOutputLevel(antOutputLevel);
336         project.addBuildListener(buildLogger);
337         project.setInputHandler(inputHandler);
338 
339         try {
340             project.executeTarget(targetName);
341             if (!textOnly) {
342                 builddialog.setFinished();
343             }
344 
345             showInfoDialog(builddialog, project.getProperties(), "antigen.outro.text", "antigen.outro.template",
346                     textOnly);
347 
348         } catch (BuildException e) {
349             logger.info("There was an error in the build process");
350             logger.info("e.getClass()");
351             logger.info("e");
352             if (textOnly) {
353                 System.out.println(e.getMessage());
354             } else {
355                 new AbortDialog(builddialog, e.getMessage()).show();
356             }
357         } finally {
358             //Only delete the builddir if it's a temporary one.
359             if (buildDir==null) {
360                 tmpDir.recursivelyDelete();
361             }
362         }
363 
364     }
365 
366     /***
367      * @param args
368      */
369     private static CommandLine parseCommandLine(String[] args) {
370         Parser parser = new PosixParser();
371         Options options = new Options();
372         options.addOption(HELP_OPTION, "help", false, "Display this help.")
373                 .addOption(CMDLINE_OPTION, "commandline", false, "Execute on the command line (without a GUI)")
374                 .addOption(DEBUG_OPTION, "debug", true,"Set the commandline debug output level - see java doc for java/util/logging/Level for values, defaults to SEVERE")
375                 .addOption(PROPERTYFILE_OPTION, "properties", true,"Supply a .property file")
376              //   .addOption(PROPERTY_OPTION, "property",true,"Supply a single property (foo.bar=value)")
377                 .addOption(PROXY_OPTION,"proxy",true,"Supply a web proxy host (host:port)")
378                 .addOption(PROXYUSER_OPTION,"user",true, "Supply a user name for the proxy (user:password)");
379         Option propertyOpt = OptionBuilder.withArgName( "property=value" )
380                 .withLongOpt("antproperty")
381                 .hasArg()
382                 .withValueSeparator()
383                 .withDescription( "use value for given property in the Ant build" )
384                 .create( PROPERTY_OPTION );
385         options.addOption(propertyOpt);
386         propertyOpt.setArgs(Option.UNLIMITED_VALUES);
387         
388         Option antigenPropertyOpt = OptionBuilder.withArgName( "property=value" )
389             .withLongOpt("antigenproperty")
390             .hasArg()
391             .withValueSeparator()
392             .withDescription( "use value for given property, overriding the value in the Antigen config.properties file" )
393             .create( PROPERTY_ANTIGEN_OPTION );
394         options.addOption(antigenPropertyOpt);
395         antigenPropertyOpt.setArgs(Option.UNLIMITED_VALUES);       
396 
397         CommandLine line = null;
398         try {
399             line = parser.parse(options, args);
400         } catch (ParseException e) {
401             logger.severe("Error parsing command line " + e.getMessage());
402             displayHelp(options);
403             System.exit(1); //@TODO this has to be done better - this is crap
404         }
405         if (line.hasOption(HELP_OPTION)) {
406             displayHelp(options);
407             System.exit(0); //@TODO this has to be done better - this is crap
408         }
409         return line;
410     }
411 
412     /***
413      * @param options
414      *  
415      */
416     private static void displayHelp(Options options) {
417         new HelpFormatter().printHelp("java -jar (jarname) option", options);
418     }
419 
420     /***
421      * @param builddialog
422      * @param properties
423      * @param textKey
424      * @param templateKey
425      * @param textOnly
426      * @throws Exception
427      * @throws ResourceNotFoundException
428      * @throws ParseErrorException
429      * @throws MethodInvocationException
430      */
431     private static void showInfoDialog(BuildLoggerFrame builddialog, Hashtable properties, String textKey,
432             String templateKey, boolean textOnly) throws Exception {
433         String text = Config.getProperty(textKey);
434         String template = Config.getProperty(templateKey);
435         String message = template != null ? MessageProcessor.processMessage(properties, template) : text;
436 
437         if (message == null) {
438             logger.fine("No information text");
439             return;
440         }
441 
442         InfoDisplay display;
443         if (textOnly) {
444             display = new TextInfoDisplay(message);
445         } else {
446             display = new InfoFrame(message);
447         }
448 
449         display.showAndWaitForResponse();
450     }
451 
452     /***
453      * Get the File that this jar is being run from. Lifted from antinstaller's
454      * SelfExtractor http://antinstaller.sourceforge.net and modified a bit
455      * 
456      * @return
457      * @throws FileNotFoundException
458      */
459 
460     private File getEnclosingJar() throws FileNotFoundException {
461         logger.fine("Attempting to get enclosing jar name");
462         String thisClass = "/" + getClass().getName().replace('.', '/') + ".class";
463         logger.finer("This class " + thisClass);
464         URL jarUrl = this.getClass().getResource(thisClass);
465         String stringForm = jarUrl.toString();
466         logger.finer("Jar URL " + stringForm);
467         String fileForm = jarUrl.getFile();
468 
469         int endIdx = stringForm.indexOf("!/");
470         if (endIdx == -1) {
471             throw new FileNotFoundException("Could not locate enclosing jar: !/ not found in URL");
472         }
473         
474         String fileNamePart = stringForm.substring("jar:file:".length(), endIdx);
475         logger.finer("Extracted file name: " + fileNamePart);
476         String unescaped = URLDecoder.decode(fileNamePart);
477         logger.finer("Unescaped Extracted file name: " + unescaped);
478         File file = new File(unescaped);
479         if (!file.exists()) {
480             throw new FileNotFoundException("Could not locate enclosing jar: file "+file+" not found");
481         }
482         return file;
483     }
484 
485 }